package com.fla.puzzle.ui.game;

import java.util.Iterator;

import com.fla.puzzle.PuzzleApplication;
import com.fla.puzzle.R;
import com.fla.puzzle.utils.AndroidUtils;

import junit.framework.Assert;

import android.content.Context;
import android.graphics.Rect;
import android.media.MediaPlayer;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.AbsoluteLayout;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

/**
 * Draws game view
 * 
 * <p>
 * Splits image onto several pieces
 * </p>
 */
public class GameView extends AbsoluteLayout {
    private static final int ANIMATION_DURATION = 500;

    private CompoundButton currentChecked;

    private GameModel gameModel;

    private FieldViewProvider fieldViewProvider;

    private OnGameFinishedListener onGameFinishedListener;

    private boolean isFinished = false;

    /**
     * Callback to be invoked when the game has been finished
     */
    public interface OnGameFinishedListener {
        void onFinished();
    }

    public GameView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public GameView(Context context) {
        super(context);
    }

    /**
     * Sets game finish listener
     * 
     * @param listener {@link OnGameFinishedListener}
     */
    public void setOnGameFinishedListener(OnGameFinishedListener listener) {
        this.onGameFinishedListener = listener;
    }

    /**
     * Shuffles current GameModel. This is long running operation.
     * 
     * @param iterationCount int indicates iteration count when reshuffling
     * @param shuffleDelay int the delay after every iteration
     */
    public void shuffle(int iterationCount, int shuffleDelay) {
        Assert.assertNotNull(gameModel);
        Assert.assertTrue(getChildCount() > 0);

        currentChecked = null;

        int count = getChildCount();

        for (int iteration = 0; iteration < iterationCount; iteration++) {
            // i: [0; count/2]
            // j: [count/2 + 1; count - 1]

            int i = (int) (Math.random() * (count / 2 + 1));
            int j = count / 2 + 1 + (int) (Math.random() * ((count - 1) / 2));

            final CompoundButton firstView = (CompoundButton) getChildAt(i);
            final CompoundButton secondView = (CompoundButton) getChildAt(j);

            post(new Runnable() {
                @Override
                public void run() {
                    swap(firstView, secondView, false);
                }
            });

            SystemClock.sleep(shuffleDelay);
        }
        Assert.assertTrue("We got new views after shuffling!", count == getChildCount());
    }

    /**
     * Applies the given model and draws it
     * 
     * @param model GameModel
     */
    public void setGameModel(GameModel gameModel) {
        Assert.assertTrue(gameModel.getOrderedFields().size() != 0);
        Assert.assertTrue(gameModel.getOrderedFields().size() == gameModel.getColsCount() * gameModel.getRowsCount());

        this.gameModel = gameModel;

        isFinished = false;

        post(new Runnable() {
            @Override
            public void run() {
                initAndRenderGame();
            }
        });
    }

    public boolean isFinished() {
        return isFinished;
    }

    // should be invoked when the size of the view is known
    private void initAndRenderGame() {
        Assert.assertTrue(getHeight() != 0 && getWidth() != 0);

        removeAllViewsInLayout();

        Log.d("test", "Rendering game model: " + gameModel);

        if (fieldViewProvider != null) {
            fieldViewProvider.closeResources();
        }
        fieldViewProvider = new FieldViewProvider(getContext(), gameModel, getWidth(), getHeight());

        Iterator<GameField> gameFieldIterator = gameModel.getOrderedFields().iterator();

        for (int row = 0; row < gameModel.getRowsCount(); row++) {
            for (int col = 0; col < gameModel.getColsCount(); col++) {
                addView(createGameFieldView(gameFieldIterator.next(), col, row));
            }
        }
    }

    private CompoundButton createGameFieldView(GameField gameField, int column, int row) {
        CompoundButton button = fieldViewProvider.createFieldView(gameField);

        int x = column * getFieldWidth();
        int y = row * getFieldHeight();

        button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, x, y));

        button.setWidth(getFieldWidth());
        button.setHeight(getFieldHeight());

        button.setOnCheckedChangeListener(new ButtonSwapController());

        return button;
    }

    private int getFieldWidth() {
        Assert.assertTrue(getWidth() != 0);

        return getWidth() / gameModel.getColsCount();
    }

    private int getFieldHeight() {
        Assert.assertTrue(getHeight() != 0);

        return getHeight() / gameModel.getRowsCount();
    }

    private void checkIfGameFinished() {
        if (gameModel.areFieldsInCorrectOrder() && onGameFinishedListener != null) {
            isFinished = true;

            if (PuzzleApplication.getInstance().isSoundEnabled()) {
                AndroidUtils.playSound(getContext(), R.raw.levelcomplete);
            }

            postDelayed(new Runnable() {
                @Override
                public void run() {
                    onGameFinishedListener.onFinished();
                }
            }, ANIMATION_DURATION);
        }
    }

    private void swap(CompoundButton button1, CompoundButton button2, boolean checkFinish) {
        if (button1 == button2) {
            return;
        }
        Assert.assertNotNull(button1);
        Assert.assertNotNull(button2);

        button1.setChecked(false);
        button2.setChecked(false);

        // order of this operations important!
        gameModel.swap(fieldViewProvider.obtainGameField(button1), fieldViewProvider.obtainGameField(button2));
        fieldViewProvider.swapContent(button1, button2);

        // show animated swap

        Rect rect1 = new Rect();
        button1.getHitRect(rect1);

        Rect rect2 = new Rect();
        button2.getHitRect(rect2);

        int xDelta = rect2.left - rect1.left;
        int yDelta = rect2.top - rect1.top;

        button1.startAnimation(createAnimation(-xDelta, -yDelta));
        button2.startAnimation(createAnimation(xDelta, yDelta));

        if (checkFinish) {
            checkIfGameFinished();
        }
    }

    private Animation createAnimation(int fromXDelta, int fromYDelta) {
        Animation animation = new TranslateAnimation(-fromXDelta, 0, -fromYDelta, 0);
        animation.setDuration(ANIMATION_DURATION);
        return animation;
    }

    private class ButtonSwapController implements OnCheckedChangeListener {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (currentChecked != null && isChecked) {
                swap(currentChecked, buttonView, true);
                currentChecked = null;
            }
            else {
                currentChecked = isChecked ? buttonView : null;
            }
        }
    }
}
